<?php
/**
 * @package Curly
 * @subpackage Collections
 * @version 0.9
 * @link http://curly.codeplex.com/
 * @license http://curly.codeplex.com/license The MIT License
 * @author Dawid Zawada
 */

namespace Curly\Collections
{
    /**
     * Represents a collection of keys and values, where keys are resources
     * and values can be of any type
     * 
     * @package Curly
 	 * @subpackage Collections
     * @license http://curly.codeplex.com/license The MIT License
     */
    class ResourceIndexedCollection extends Collection
    {       
        /**
         * Returns the value associated with the given key or
         * null if not found
         * 
         * The key must be of resource type
         * 
         * @param resource $key
         * @return mixed
         * @throws \InvalidArgumentException
         */
        public function get( $key )
        {
            $this->validateKey( $key );
            
            $internalKey = $this->findItem( $key );
            if( $internalKey !== false )
            {
                return $this->items[$internalKey]->value;
            }
            else
            {
                return null;
            }
        }
        
        /**
         * Returns all items of this collection as an array of 
         * KeyValuePair objects
         * 
         * @return array
         */
        public function getAll()
        {
            return $this->items;
        }
        
        /**
         * Returns all keys of this collection
         *
         * @return array
         */
        public function getKeys()
        {
            $items = $this->items;
            array_walk( $items, function( &$item ) {
                $item = $item->key;
            } );
            
            return array_values( $items );
        }
        
        /**
         * Returns all values of this collection
         *
         * @return array
         */
        public function getValues()
        {
            $items = $this->items;
            array_walk( $items, function( &$item ) {
                $item = $item->value;
            } );
            
            return array_values( $items );
        }
        
        /**
         * Adds or updates the value associated with the given key
         * 
         * The key must be of resource type.
         * The method returns $this object, so you can do
         * method chaining.
         * 
         * @param resource $key
         * @param mixed $value
         * @return Curly\Collections\ResourceIndexedCollection
         * @throws \InvalidArgumentException
         */
        public function set( $key, $value )
        {
            $this->validateKey( $key );
            
            $internalKey = $this->findItem( $key );
            if( $internalKey !== false )
            {
                $this->items[$internalKey]->value = $value;
            }
            else
            {
                $this->items[] = new KeyValuePair( $key, $value );
            }
            
            return $this;
        }
        
        /**
         * Merges this collection with the given array of 
         * KeyValuePair objects
         * 
         * If the array has a key that already exists 
         * in this collecion, the value associated with that key
         * will overwrite the one stored in this collection.
         * The method returns $this object, so you can do
         * method chaining.
         * 
         * @param array $items
         * @return Curly\Collections\ResourceIndexedCollection
         * @throws \UnexpectedValueException
         */
        public function setMultiple( array $items )
        {
            try
            {
                foreach( $items as $i => $item )
                {
                    if( !( $item instanceof KeyValuePair ) )
                    {
                        throw new \UnexpectedValueException( "All elements of the given \$items array must be of type KeyValuePair. '" . gettype( $item ) . "' type found at index {$i}" );
                    }
                    
                    $this->set( $item->key, $item->value );
                }
            }
            catch( \InvalidArgumentException $e )
            {
                throw new \UnexpectedValueException( "At least one key of the given key-value pairs has an invalid type", null, $e );
            }
        }
        
        /**
         * Removes the element with the given key from this collection
         * 
         * The key must be of resource type.
         * This method returns true if the element is successfully found 
         * and removed, false otherwise
         * 
         * @param resource $key
         * @return bool
         * @throws \InvalidArgumentException
         */
        public function remove( $key )
        {
            $this->validateKey( $key );
            
            $internalKey = $this->findItem( $key );
            if( $internalKey !== false )
            {
                unset( $this->items[$internalKey] );
                return true;
            }
            else
            {
                return false;
            }
        }
        
        /**
         * Determines whether this collection contains the given key
         * 
         * The key must be of resource type
         * 
         * @param resource $key
         * @return bool
         */
        public function containsKey( $key )
        {
            $this->validateKey( $key );
            
            return $this->findItem( $key ) !== false;
        }
        
        /**
         * Determines whether this collection contains the given value
         * 
         * @param mixed $value
         * @return bool
         */
        public function containsValue( $value )
        {
            foreach( $this->items as $internalKey => $item )
            {
                if( $item->value == $value )
                {
                    return true;
                }
            }
            
            return false;
        }
        
        /**
         * Throws an exception if the given key is not of resource type
         * 
         * @param resource $key
         * @throws \InvalidArgumentException
         */
        protected function validateKey( $key )
        {
            if( !is_resource( $key ) )
            {
                throw new \InvalidArgumentException( "Key must be a resource, '" . gettype( $key ) . "' given" );
            }
        }
        
        /**
         * Returns the internal, integer-type key associated with 
         * the KeyValuePair object, that stores the given resource
         * type key
         * 
         * The method returns false if the given key is not found
         * 
         * @param resource $key
         * @return mixed
         */
        protected function findItem( $key )
        {           
            foreach( $this->items as $internalKey => $item )
            {
                if( $item->key == $key )
                {
                    return $internalKey;
                }
            }
            
            return false;
        }
    }
}